//AuthenticationServer.cc
// author: Gupo>
// Modifications by dipina@eside.deusto.es: 


#include "AuthenticationServer.h"

#define MSGBUFSIZE 1024

extern "C" {
#include </usr/include/signal.h>
}

namespace PracticaCaso {
	AuthenticationServer::AuthenticationServer(int p, string m): TcpListener(p)
	{
		// Process the contents of the mapping file
		this->sqliteMap = new SQLiteMap(m+"_cache.db");
		this->loadMappings(m);
		// For global event observation, you must activate the mDNSObserver thread here.
		// But... for local event observation, you ought to use one different observer for each mDNS request.
		this->observer = new mDNSObserver (this);
		this->observer->start();
		// Create the mDNSQueryWrapper.
		this->queryWrapper = new mDNSQueryWrapper (this);
		
		codProcesoIndex=0;
	}
	AuthenticationServer::AuthenticationServer(const AuthenticationServer& ns): TcpListener(ns.port)
	{
		domain = ns.domain;
		dns2IpPortValidMap = ns.dns2IpPortValidMap;
		server_socket = ns.server_socket;
		port = ns.port;
		sqliteMap = ns.sqliteMap;
		//leerCache = ns.leerCache;
		// Feed the random number generator with some real random data: system time, por instance.
		randSeed = (int)time(0);
	}
	AuthenticationServer::~AuthenticationServer()
	{
		delete this->sqliteMap;
		delete this->observer;
	}
	AuthenticationServer& AuthenticationServer::operator = (const AuthenticationServer& rhs)
	{
		// Check for identity.
		if (this == &rhs) return *this;
		domain = rhs.domain;
		dns2IpPortValidMap = rhs.dns2IpPortValidMap;
		server_socket = rhs.server_socket;
		port = rhs.port;
		sqliteMap = rhs.sqliteMap;
		//leerCache = rhs.leerCache;
		return *this;
	}
	void AuthenticationServer::loadMappings(string mappingsFileName)
	{
		/*
		 
		 while (!in.eof()) {
		 in.getline(line, 1024, '\n');
		 // Process the line.
		 if (((string)line).find("#") == 0) {
		 continue;
		 }
		 istringstream ist(line);
		 string word[2];
		 ist >> word[0];
		 if (!(ist >> word[1])) {
		 if (this->domain.length() == 0) {
		 this->domain = word[0];
		 }
		 } else {
		 this->dns2IpPortValidMap[word[0]] = word[1];
		 }
		 }
		 in.close();
		 */
		
		// Retrieve all the mapping previously saved.
		typedef map<string, string>::const_iterator CI;
		map<string, string> cachedSqliteMap = this->sqliteMap->getMap();
		for (CI p = cachedSqliteMap.begin(); p != cachedSqliteMap.end(); ++p) {
			this->dns2IpPortValidMap[p->first] = p->second;
			cout << "CACHED dns2IpPortMapValid[" << p->first << "] = " << p->second << endl;
			
		} 
	}
	mDNSQueryWrapper::mDNSQueryWrapper(AuthenticationServer* c): client(c)
	{
		// Setup the mDNSClient multicast socket. Something similar to an UDP standard socket.
		// Once configured, you only need to call queryWrapper->send(string str) method.
		if ((fd4Sending=socket(AF_INET,SOCK_DGRAM,0)) < 0) {
			cerr << "Problems creating sending multicast socket" << endl;
			exit(1);
		}
		// Set up multicast group destination address for sending. (It's the same than UDP datagram sending, with an special destination address.)
		memset(&addr,0,sizeof(addr));
		addr.sin_family=AF_INET;
		addr.sin_addr.s_addr=inet_addr(MDNS_GROUP);
		addr.sin_port=htons(MDNS_PORT);
	}
	void mDNSQueryWrapper::send(string str) 
	{
		int result=0;
		// Send the requests through the multicast UDP socket.
		result=sendto(fd4Sending, str.c_str(),str.size(),0,(struct sockaddr *) &addr, sizeof(addr));
		if (result < 0) {
			cerr << "ERROR: sendto in delegatemDNSCommunity()" << endl;
			perror("hola");
			exit(1);
		}
	}
	mDNSObserver::mDNSObserver(AuthenticationServer* c): client(c), keepRunning(true)
	{
		struct ip_mreq mreq;
		u_int yes=1;
		// Set up multicast group destination address for receiving.
		if ((fd4Receiving=socket(AF_INET,SOCK_DGRAM,0)) < 0) {
			cerr << "Problems creating receiving multicast socket" << endl;
			exit(1);
		}
		// Trick just to allow multiple sockets to use the same PORT number: Only to try several processes in the same machine.
		if (setsockopt(fd4Receiving,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes)) < 0) {
			cerr << "Problems: Reusing ADDR failed" << endl;
			exit(1);
		}
		// Set up destination address.
		memset(&addr,0,sizeof(addr));
		addr.sin_family=AF_INET;
		addr.sin_addr.s_addr=htonl(INADDR_ANY); /* N.B.: differs from sender */
		addr.sin_port=htons(MDNS_PORT);
		
		cout << "A punto de hacer a bind de multicast." << endl;
		
		// Bind to receive address.
		if (bind(fd4Receiving,(struct sockaddr *) &addr,sizeof(addr)) < 0) {
			cerr << "Problems with bind for multicast UDP socket" << endl;
			exit(1);
		}
		// Use setsockopt() to request that the kernel join a multicast group.
		mreq.imr_multiaddr.s_addr=inet_addr(MDNS_GROUP);
		mreq.imr_interface.s_addr=htonl(INADDR_ANY);
		if (setsockopt(fd4Receiving,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq)) < 0) {
			cerr << "Problems with setsockopt setup" << endl;
			exit(1);
		}
	}
	void mDNSObserver::run()
	{
		int nbytes,addrlen;
		char msgbuf[MSGBUFSIZE];
		cout << "mDNSObserver: running" << endl;
		memset(msgbuf,0,sizeof(msgbuf));
		// Now just enter a read-print loop.
		while (this->keepRunning) {
			addrlen=sizeof(addr);
			// Read one multicast UDP packet.
			if ((nbytes=recvfrom(fd4Receiving,msgbuf,MSGBUFSIZE,0, (struct sockaddr *)&addr,
								 (socklen_t*)&addrlen)) < 0) {
				cerr << "recvfrom" << endl;
				exit(1);
			}
			cout << "mDNSObserver: event received: " << msgbuf << " " << endl;
			// Process the multicast UDP messages with the utility mdns_management function.
			istringstream ins;
			ins.str(msgbuf);
			string command, payload, code; // Payload can be one dnsName string or one serverDetails string.
			// Parse a little bit event parameters.
			// Three parameters: Command, Payload [dnsName or IpPort string], and random verification-code.
			ins >> command >> payload >> code;
			client->mdns_management(command, payload, code);
			memset(msgbuf, 0, sizeof(msgbuf));//libera memoria del buffer
		}	
	}
	void mDNSObserver::stop() 
	{
		this->keepRunning = false;
	}
	mDNSObserver::~mDNSObserver()
	{
		this->stop();
	}
	
	
	
	void AuthenticationServer::waitValidates(string cmd, string payload, string code)
	{	clock_t timeStamp1, timeStamp2;//dfinen el time out
		bool tooMuchTime=false;
		double elapsedTime=0.0;
		timeStamp1=time(0);
		cout << "AuthenticationServer: entering VALIDATE waiting loop. TimeStamp1: " << timeStamp1 << " " << endl;
		while (!tooMuchTime)
		{
			timeStamp2=time(0);
			cout << "delegatemDNSCommunity: Waiting VALIDATE. TimeStamp2: " << timeStamp2 << ", tooMuchTime: " << tooMuchTime << " " << endl;			
			elapsedTime=difftime(timeStamp2, timeStamp1);
			if (elapsedTime>=MDNS_TIMEOUT_AUTH) 
			{
				tooMuchTime=true;
			}
			sleep(1);
		}
		cout<< "CODE: "<<code <<" PAYLOAD: "<<payload<< "  "<< cmd<< endl;
		map<string, map<string, int> >::iterator p;
		cout<< "1";
		p = code2IpPortMap.find(code);
		cout<< "2CODE: "<<code <<" PAYLOAD: "<<payload<< "  "<< cmd<< endl;
		if(p!=code2IpPortMap.end())
		{
		map<string, int>::iterator q;
		cout<< "3CODE: "<<code <<" PAYLOAD: "<<payload<< "  "<< cmd<< endl;
		map<string,int> mapaCodigo;
		cout<< "4CODE: "<<code <<" PAYLOAD: "<<payload<< "  "<< cmd<< endl;
		mapaCodigo = p->second;
		cout<< "5CODE: "<<code <<" PAYLOAD: "<<payload<< "  "<< cmd<< endl;
		q = mapaCodigo.find(payload);
		if(q!=mapaCodigo.end())
		{
			int cont = q->second;
			cont++;
			q->second= cont;
			
		}
		else {
			mapaCodigo[payload]=1;
		}
		}
		else{
			map<string, int> temp;
			temp[payload]=1;
			code2IpPortMap[code]=temp; 
		}
		mdns_manage_response (cmd,payload,code);		

	
	
	
	}
	
	
	void AuthenticationServer::mdns_management(string cmd, string payload, string code) 
	{	bool exists=false;
		//map<string, string>::iterator p;
		//string dnsValue;
		// Begin management utility function.
		if(cmd=="MDNS_VALIDATE")
		{
			if (atoi(code.c_str())<0) 
			{
				//ALMACENAR VALIDATE CARGAR dns2IpPortMap
				for (int i=0; i<codProcesoIndex; i++)
				{
					if (codProceso[i]==atoi(code.c_str())) 
					{
					exists=true;
					}
				}
				
				
					if (exists) 
					{
						map<string, map<string, int> >::iterator p;
						p = code2IpPortMap.find(code);
						
						map<string, int>::iterator q;
						map<string,int> mapaCodigo;
						mapaCodigo = p->second;
						q = mapaCodigo.find(payload);
						if(q!=mapaCodigo.end())
						{
							int cont = q->second;
							cont++;
							q->second= cont;
							
						}
						else {
							mapaCodigo[payload]=1;
						}

						//guardar ippuerto
					}
					else
					{	codProcesoIndex++;
						codProceso[codProcesoIndex]=atoi(code.c_str());
						waitValidates(cmd,payload,code) ;
						
						
						//guardarippuerto
					}

				}
				cout << "AUTH SERVER: VALIDATE accepted code:" << code<<endl; 
			}
			
		
		if (cmd=="MDNS_REQUEST")
		{
			mdns_manage_request(cmd,payload,code);
			
		}
		
		
	}
	int AuthenticationServer::decypherCode(string code)
	{
		return -atoi(code.c_str());
	}
	void AuthenticationServer::mdns_manage_response(string cmd, string payload, string code) 
	{
		cout<< "CODE antes: "<<code <<" PAYLOAD: "<<payload;
		cout<< "CODE: "<<code <<" PAYLOAD: "<<payload;

		//Recognize negative code
			
		string valid =Validate(payload,code);
			if(valid =="")
			{
				
				cout<< "dentro del if valid: "<< valid<<endl;
				//devolvemos Ip falsa y code positivo cuando esta mal
				
			}
			else {
				//devolvemos IP  y code positivo cuando esta bien
				ostringstream query;
				int codigoValido = decypherCode(code);
				query << MDNS_RESPONSE << " " << payload << " " << codigoValido;
				cout<<"el codigo de la response es: "<< codigoValido<<endl;
				cout << "A—ADIDO A LA BLACKLIST" << query.str() << " " << endl;
				queryWrapper->send(query.str());
			}
			
			
			//	solvedQuery = payload;
			//	satisfiedQuery= true;
			
			/////////////comprobar si es verdad//////////////////////////////////
		
	}
	void AuthenticationServer::mdns_manage_request(string cmd, string payload, string code)
	{
		
		//Save the payload in the dns2CodeMap
		cout <<"LOOOOSOSOSLSOAOSLDIDKSLSLS" <<code;
		cout << "MDNS response " << "Code saved" <<endl;
		code2DnsMap [code] = payload ;

		
	}
	string AuthenticationServer::delegatemDNSCommunity(string dnsName) 
	{
		// Several temporization parameters.
		clock_t timeStamp1, timeStamp2;//dfinen el time out
		bool tooMuchTime=false;
		double elapsedTime=0.0;
		// Initialize query parameters.
		satisfiedQuery=false;
		solvedQuery="";
		pendingQuery=dnsName;
		ostringstream temp;
		// Prepare the random number generator initial seed. Por example, system time.
		temp << (int)rand()%1024; // Certain control, numbers between 0 and 1023. This can be changed.
		pendingQueryCode=temp.str();
		cout << "delegatemDNSCommunity: generating random code...ok: code=" << pendingQueryCode << " " << endl;
		// Send the request using the mDNSQueryWrapper.
		ostringstream query;
		// Build the MDNS_REQUEST. Three parameters: Command, dnsName, and random verification code.
		query << MDNS_REQUEST << " " << dnsName << " " << pendingQueryCode;
		cout << "delegatemDNSCommunity: entering queryWrapper->send(query.str()): " << query.str() << " " << endl;
		queryWrapper->send(query.str());
		// Wait for one or many MDNS_RESPONSES.
		// Active waiting is only the first approach. Control the elapsed time with two timestamps.
		timeStamp1=time(0);
		cout << "delegatemDNSCommunity: entering MDNS_RESPONSES waiting loop. TimeStamp1: " << timeStamp1 << " " << endl;
		while (!satisfiedQuery && !tooMuchTime) {
			timeStamp2=time(0);
			cout << "delegatemDNSCommunity: Waiting MDSN_RESPONSES. TimeStamp2: " << timeStamp2 << ", tooMuchTime: " << tooMuchTime << " " << endl;			
			elapsedTime=difftime(timeStamp2, timeStamp1);
			if (elapsedTime>=MDNS_TIMEOUT) {
				tooMuchTime=true;
				if (!satisfiedQuery) solvedQuery="MDNS_TIMEOUT ERROR";
			}
			sleep(1);
		}
		// And return the resulting ipAddressAndPort string.
		return(solvedQuery);
	}
	string AuthenticationServer::delegateExternalDnsServer(string serverDetails, string dnsName) 
	{
		string ipAddressServer;
		int portServer;
		if (serverDetails.find(":") >=0) {
			ipAddressServer = serverDetails.substr(0, serverDetails.find(":"));
			portServer = atoi((serverDetails.substr(serverDetails.find(":")+1)).c_str());
		}
		PracticaCaso::TcpClient clientDns;
		clientDns.connect(ipAddressServer, portServer);
		// Lookup inAuthenticationServer
		clientDns.send(dnsName);
		string ipAddressAndPort = clientDns.receive();
		if (ipAddressAndPort.find("ERROR") == 0) {
			cout << "The DNS name " << dnsName << " could not be resolved." << endl;
			ipAddressAndPort = "ERROR: The DNS name " + dnsName + " could not be resolved.";
		} else {
			cout << "The DNS name: " << dnsName << " corresponds to: " << ipAddressAndPort << endl;
		}
		clientDns.close();
		return ipAddressAndPort;
	}
	string AuthenticationServer::Validate(string payload,string code)
	{	
		map<string, string>::iterator p;
		string dnsValue;
		cout<< "CODE: "<<code <<" PAYLOAD: "<<payload;
		cout<< "1.-las estadisticas nos han devuelto: "<<endl;
		p = code2DnsMap.find(code);
		cout<< "2.-las estadisticas nos han devuelto: "<<endl;
		if (p != this->code2DnsMap.end()) 
		{//the code exists in requested map
			dnsValue = p->second;
			map<string, string>::iterator q;
			string ipValue;
			cout << "Antes de statisc";
			string stadistics = stadistic(code);
			cout<< "3.-las estadisticas nos han devuelto: "<< stadistics<<endl;
			/*q = (this->dns2IpPortValidMap).find(dnsValue);
			if (q != this->dns2IpPortValidMap.end()) 
			{
				ipValue = q->second;
				if(payload == ipValue)
				{	
					cout << "retornamos el payload: " << payload<< endl;
					return payload;
				}
				else 
				{// ip not found in the authentication server
					return "";
				}
				
				
			}
			 */
			return stadistics;
			
		}//
		// DNS not found so return false
		return "";
	}
	
	string AuthenticationServer::stadistic(string code)
	{cout << "dentro stadistic"<<endl;
		map<string, map<string, int > >::iterator p;
		p = (this->code2IpPortMap).find(code);
		if(p != code2IpPortMap.end())
		{
			map<string,int> temporales= p->second;
			int max = 0;
			string validate="";
			bool jodidos =false;
			cout << "dentro stadistic antes de for"<<endl;
			typedef map<string, int>::const_iterator CI;
            for (CI q = temporales.begin(); q !=temporales.end(); ++q) 
			{
				if(max< (q->second))
				{
					cout << "dentro de stadistics en max<q"<<endl;
					max=q->second;
					validate = q->first;
					jodidos= false;
				}
				else
					if(max = q->second)
					{
						jodidos=true;
					}
				if(jodidos)
				{
					return "";
				}
				else
				{
					return validate;
				}
            }
			return "";
		}
		else
		{
			return "";
		}
	}
			
	
	
	AuthenticationServerThread::~AuthenticationServerThread()
	{
		delete this->dnsClient;
	}
	void AuthenticationServerThread::run() 
	{
		// make the type casting and recuperate the parameters using "arg"
	//	string dnsEntry = (this->dnsClient)->receive();
	//	string ipAddressAndPort = (this->dnsServer).Validate(dnsEntry);
	//	(this->dnsClient)->send(ipAddressAndPort);
	//	cout << "DNS resolution for: " << dnsEntry << " is: " << ipAddressAndPort << endl;
	//	(this->dnsClient)->close();
	}
	ostream & operator << (ostream & os,AuthenticationServer &t) 
	{
		os << "server_socket: " << t.server_socket << " - port: " << t.port << " - domain: " << t.domain << endl;
		os << "DNS mappings:" << endl;
		typedef map<string, string>::const_iterator CI;
		for (CI p = t.dns2IpPortValidMap.begin(); p != t.dns2IpPortValidMap.end(); ++p) {
			os <<  p->first << " : " << p->second << endl;
		}
		return os;
	}
	
}

// global variable
PracticaCaso::AuthenticationServer * AuthenticationServer_pointer;

// function called when CTRL-C is pressed
void ctrl_c(int)
{
    printf("\nCTRL-C was pressed...\n");
	AuthenticationServer_pointer->stop();
	
}

void usage() {
	cout << "Usage:AuthenticationServer <port> <name-mappings-file> [leer-fichero-cache --> true | false]" << endl;
	exit(1);
}

/*
 void processClientRequest(PracticaCaso::TcpClient *dnsClient, PracticaCaso::mNameServer* dnsServer) {
 string dnsEntry = dnsClient->receive();
 string ipAddressAndPort = dnsServer->Validate(dnsEntry);
 dnsClient->send(ipAddressAndPort);
 cout << "DNS resolution for: " << dnsEntry << " is: " << ipAddressAndPort << endl;
 dnsClient->close();
 }
 //processClientRequest(client, &nameServer);
 */

 
 
int main(int argc, char** argv) {
	signal(SIGINT,ctrl_c);
	
	if (argc != 3 && argc != 4) {
		usage();
	}
	
	
	bool leerCache = true;
	if ((argc == 4) && (string(argv[3]) == "false")) {
		leerCache = false;
	} 
	
	PracticaCaso::AuthenticationServer nameServer(atoi(argv[1]), (string)argv[2]);
	cout << "AuthenticationServer instance: " << endl << nameServer << endl;
	AuthenticationServer_pointer = &nameServer;
	nameServer.start();
	while (true) {
		PracticaCaso::TcpClient *client = nameServer.acceptTcpClient();
		PracticaCaso::AuthenticationServerThread* t = new PracticaCaso::AuthenticationServerThread(client, nameServer);
		t->start();
	}
	nameServer.stop();
}
